<template>
    <div ref="wrapper" class="virtual-list-wrapper" @scroll="handleScroll">
        <div class="virtual-list-phantom" :style="{ height: totalHeight + 'px' }"></div>
        <div class="virtual-list" :style="{ transform: `translateY(${offset}px)` }">
            <slot v-for="(item, index) in visibleData" :item="item" :index="index">
                <!-- 用于渲染列表项的插槽 -->
            </slot>
        </div>
    </div>
</template>
  
<script setup lang="ts">
import { ref, onMounted, reactive, computed, watch, toRefs } from 'vue';

const props = defineProps({
    items: {        // 整个列表的数据
        type: Array,
        default: [],
    },
    itemHeight: {      // 每个列表项的高度
        type: Number,
        default: 30,
    },
    bufferSize:{       // 在可见项周围额外渲染的列表项数量
        type: Number,
        default: 20,
    }
});

const state = reactive({
    visibleStartIndex: 0,  // 可见数据起始索引
    visibleEndIndex: 10,   // 可见数据结束索引
});

const wrapper = ref<HTMLElement | null>(null);

const { items } = toRefs(props);

// 总高度计算
const totalHeight = computed(() => {
    return props.items.length * props.itemHeight;
});

// 可见数据计算
const visibleData = computed(() => {
    return props.items.slice(state.visibleStartIndex, state.visibleEndIndex);
});

// console.log(visibleData);

function handleScroll(event: { target: any; }) {
    const scrollTop = event.target.scrollTop;

    // 计算当前滚动位置的索引
    const currentStartIndex = Math.floor(scrollTop / props.itemHeight);
    const visibleItemCount = Math.ceil((wrapper.value?.clientHeight || 0) / props.itemHeight);

    // 更新可见数据起始和结束索引
    state.visibleStartIndex = Math.max(0, currentStartIndex - props.bufferSize);
    state.visibleEndIndex = Math.min(props.items.length, currentStartIndex + visibleItemCount + props.bufferSize);
}

// 偏移量计算
const offset = computed(() => {
    return state.visibleStartIndex * props.itemHeight;
});

// 初始化和更新滚动处理器
onMounted(() => {
    const visibleItemCount = Math.ceil((wrapper.value?.clientHeight || 0) / props.itemHeight);
    state.visibleEndIndex = visibleItemCount + props.bufferSize;
});

// 监听items变化更新滚动
watch(items, () => {
    handleScroll({ target: wrapper.value });
});
</script>
  
<style>
.virtual-list-wrapper {
    overflow: inherit;
    overflow: inherit;
    position: relative;
    height: 100%;
    /* 容器高度，需要手动设置 */
}

.virtual-list-phantom {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: -1;
}

.virtual-list {
    position: absolute;
    top: 0;
    left: 0;
}
</style>